package org.hackystat.sdsa.analyzer.control;

import java.util.HashMap;

import org.hackystat.sdsa.analyzer.SdsaAnalyzerException;

/**
 * Interprets inference process of TDD episode. 
 *  
 * @author Hongbing Kou
 */
public class TDDEpisodeInterpreter {
  /** Episode type pattern map. */
  private HashMap<String, String> episodeTypePatternMap;
  /** Episode category and its type description. */
  private HashMap<String, String> episodeTypeDescMap;

  /**
   * Constructor. 
   *
   */
  public TDDEpisodeInterpreter() {
    this.episodeTypePatternMap = new HashMap<String, String>();
    this.episodeTypeDescMap = new HashMap<String, String>();
    addTestFirstPatterns(); 
    addRefactoringPatterns();
    addTestLastPatterns();
    addTestAdditionPatterns();
    addRegressionPatterns();
    addProductionPatterns();
    addLongPatterns();
    addUnknownPatterns();
  }

  /**
   * Adds patterns for test-first.
   *
   */
  private void addTestFirstPatterns() {
    String testFirstBehavior1 = "test-first:1";

    String testAddedString = "Some tests were added (%1). ";
    
    // Test-first type 1
    this.episodeTypeDescMap.put(testFirstBehavior1, "This episode looks like a typical " + 
                                                "test-first episode because:");
    //String testFirstType1Pattern = "There is a new test added at (%1) " +
    //  ", and compilation error at (%2) before production edited at (%3)" +
    //  ", and unit test failure at (%4) before production edited at (%5).";

    String testFirstType1Pattern = testAddedString +
                                   "Then a compilation error occurred (%2). " + 
                                   "Then production code was edited (%3). " +
                                   "Then tests were run with failures (%4). " + 
                                   "Then production code was again edited (%5).";

    this.episodeTypePatternMap.put(testFirstBehavior1, testFirstType1Pattern);
    
    // Test first type 2
    String testFirstBehavior2 = "test-first:2";
    String atypicalTestFirstString = 
      "This episode looks like an atypical test-first episode because:";

    this.episodeTypeDescMap.put(testFirstBehavior2, 
        atypicalTestFirstString);
    //String testFirstType2Pattern = "There is a new test added at (%1)" + 
    //  ", and compilation error at (%2) before production edited at (%3)" + 
    //  ", but there is no test failure.";

    String testFirstType2Pattern = testAddedString + 
                                   "Then a compilation error occurred (%2). " +
                                   "Then production code was added (%3). " + 
                                   "However, tests ran without failure.";
    this.episodeTypePatternMap.put(testFirstBehavior2, testFirstType2Pattern);

    // Test first type 3
    String testFirstBehavior3 = "test-first:3"; 
    this.episodeTypeDescMap.put(testFirstBehavior3, atypicalTestFirstString);
    //String testFirstType3Pattern = "There is a new test added at (%1)" +
    //  ", and test failure at (%2) before production edited at (%3)" +
    //  ", but no compilation error.";
    String testFirstType3Pattern = testAddedString +
                                   "Then tests ran with failures (%2). " + 
                                   "Then production code was edited (%3). " +
                                   "However, no compilation error occurred on test code.";
    this.episodeTypePatternMap.put(testFirstBehavior3, testFirstType3Pattern);

    // Test first type 4
    String testFirstBehavior4 = "test-first:4";
    this.episodeTypeDescMap.put(testFirstBehavior4, atypicalTestFirstString);
    //String testFirstType4Pattern = "There is new test added at (%1)" +
    //  ", and production edited at (%2)" +
    //  ", but there is neither compilation error nor unit test failure before production edit.";
    String testFirstType4Pattern = testAddedString +
                                   "Then production code was edited (%2). " +
                                   "However, neither compilation error occurred nor " + 
                                   "tests ran with failure.";
    this.episodeTypePatternMap.put(testFirstBehavior4, testFirstType4Pattern);
  }
  
  /**
   * Adds pattern for refactoring.
   *
   */
  private void addRefactoringPatterns() {
    String refactoringBehavior1A = "refactoring:1A";
    
    // Refactoring type 1A
    this.episodeTypeDescMap.put(refactoringBehavior1A, "This episode looks like a refactoring " +
                                                 "episode because:");
    String refactoringType1APattern = "Test file ``%2\" was edited (%1). " + 
                                      "However, no new test or production methods were added.";
    this.episodeTypePatternMap.put(refactoringBehavior1A, refactoringType1APattern);

    // Refactoring type 1B
    String refactoringBehavior1B = "refactoring:1B";
    this.episodeTypeDescMap.put(refactoringBehavior1B, 
        "This episode is a refactoring episode because:");
    String refactoringType1BPattern = "Test file ``%2\" was refactored (%1).";
    this.episodeTypePatternMap.put(refactoringBehavior1B, refactoringType1BPattern);

    // Refactoring type 2A
    String refactoringBehavior2A = "refactoring:2A";
    this.episodeTypeDescMap.put(refactoringBehavior2A, "This episode looks like a refactoring " + 
                                                 "episode because:");
    //String refactoringType2APattern = "There is method decrease [%1]" +  
    //                          " or statement stable/decrease [%2]" +
    //                          " on production file ``%3\".";
    String refactoringType2APattern = "The number of statements or methods of production file " + 
                                      "``%1\" either decreased or remained stable.";
    this.episodeTypePatternMap.put(refactoringBehavior2A, refactoringType2APattern);
    
    // Refactoring type 2B
    String refactoringBehavior2B = "refactoring:2B";
    this.episodeTypeDescMap.put(refactoringBehavior2B, 
        "This episode is a refactoring episode because:");
    String refactoringType2BPattern = "Production file ``%2\" was refactored (%1).";
    this.episodeTypePatternMap.put(refactoringBehavior2B, refactoringType2BPattern);

    // Refactoring type 2M, which has multiple edits.
    String refactoringBehavior2M = "refactoring:2M";
    this.episodeTypeDescMap.put(refactoringBehavior2M, 
        "This episode is a refactoring episode because:");
    String refactoringType2MPattern = "Multiple product file edits. " + 
                                      "However, neither method nor statement increases.";
    this.episodeTypePatternMap.put(refactoringBehavior2M, refactoringType2MPattern);
    
    // Refactoring type 3
    String refactoringBehavior3 = "refactoring:3";
    this.episodeTypeDescMap.put(refactoringBehavior3, 
        "This episode looks like a refactoring episode because:");
    //String refactoringType3Pattern = "There is production refactoring at (%1)" + 
    //    " on production file ``%2\"" +
    //    ", and test refactoring at (%3) on test file ``%4\".";
    
    String refactoringType3Pattern = "Production file ``%2\" was edited (%1). " + 
                                     "Test file ``%4\" was edited (%3). " + 
                                     "However, no new tests or production methods were added.";
    this.episodeTypePatternMap.put(refactoringBehavior3, refactoringType3Pattern);
  }
  
  /**
   * Adds pattern for test-last episodes.
   *
   */
  private void addTestLastPatterns() {
    String testLastBehavior = "test-last";
    // Test-last type 1
    this.episodeTypeDescMap.put(testLastBehavior + ":1", 
        "This episode looks like a typical test-last episode because:");
    String testLastType1Pattern = "Some tests were added (%1) after production code " + 
                                  "was edited (%2)."; 
    this.episodeTypePatternMap.put(testLastBehavior + ":1", testLastType1Pattern);
  }
  
  /**
   * Adds pattern for test-addition episodes.
   *
   */
  private void addTestAdditionPatterns() {
    String testAdditionBehavior1 = "test-addition:1";
    // Test-addition type 1
    this.episodeTypeDescMap.put(testAdditionBehavior1, 
        "This episode looks like it is all about adding new tests because:");
    //String testTestAddition1Pattern = "There is new test added at (%1), but no production edit."; 
    String testTestAddition1Pattern = "New tests were added (%1), but no production code " + 
                                      "was modified.";
    
    this.episodeTypePatternMap.put(testAdditionBehavior1, testTestAddition1Pattern);
    
    String testAdditionBehavior2 = "test-addition:2";
    // Test-addition type 2
    this.episodeTypeDescMap.put(testAdditionBehavior2, 
        "This episode looks like it is all about adding new tests because:");
    String testTestAddition2Pattern = "New tests were added (%1) and tests failed (%2), " +
                                      "but no production code was modified."; 
    this.episodeTypePatternMap.put(testAdditionBehavior2, testTestAddition2Pattern);    
  }
  
  /**
   * Adds pattern for regression episodes.
   *
   */
  private void addRegressionPatterns() {
    // Regression type 1
    //this.episodeTypeDescMap.put("regression:1", "This episode looks like it is all about " + 
    //                                            "running tests:");
    String regressionBehavior1 = "regression:1";
    this.episodeTypeDescMap.put(regressionBehavior1, "");
    // No explanation necessary.
    String testRegression1Pattern = "";
    this.episodeTypePatternMap.put(regressionBehavior1, testRegression1Pattern);

    // Regression type 2
    String regressionBehavior2 = "regression:2";
    this.episodeTypeDescMap.put(regressionBehavior2, 
        "This episode looks like it is all about running tests:");
    String testRegression2Pattern = 
      "Some tests ran with failures or they could not compile due to test settings.";
    this.episodeTypePatternMap.put(regressionBehavior2, testRegression2Pattern);
  }
  
  /**
   * Adds pattern for production episodes.
   *
   */
  private void addProductionPatterns() {
    String productionBehavior1 = "production:1";
    String productionBehaviorString = "This episode looks like a production episode because:";
    
    // Production type 1
    this.episodeTypeDescMap.put(productionBehavior1, productionBehaviorString);

    String testProduction1Pattern = "More statements were added to production file ``%1\". " + 
                                    "However, no test was added or edited.";
    this.episodeTypePatternMap.put(productionBehavior1, testProduction1Pattern);

    
    // Production type 1M
    String productionBehavior1M = "production:1M";
    this.episodeTypeDescMap.put(productionBehavior1M, productionBehaviorString);
    String testProduction1MPattern = "There are multiple product file edits, but no method " +
                                     "change, and statement decreases.";
    this.episodeTypePatternMap.put(productionBehavior1M, testProduction1MPattern);
    
    // Production type 2
    String productionBehavior2 = "production:2"; 
    this.episodeTypeDescMap.put(productionBehavior2, productionBehaviorString);
    
    String testProduction2Pattern = "More methods were added to production file ``%1\". " + 
                                    "However, no test was added or edited.";
    this.episodeTypePatternMap.put(productionBehavior2, testProduction2Pattern);

    // Production type 2M
    String productionBehavior2M = "production:2M";     
    this.episodeTypeDescMap.put(productionBehavior2M, productionBehaviorString);
    
    String testProduction2MPattern = "There are multiple product file edits, and method " +
                                      "increases, but file size increases less than 100 bytes.";
    this.episodeTypePatternMap.put(productionBehavior2M, testProduction2MPattern);

    
    // Production type 3
    String productionBehavior3 = "production:3";
    this.episodeTypeDescMap.put(productionBehavior3, 
        "This episode looks like a production episode in test-last because:");
    String testProduction3Pattern = "More methods were added to production file ``%1\". " + 
                                    "And more than 100 bytes of production code were added. " + 
                                    "Furthermore, no test was added or edited.";
    this.episodeTypePatternMap.put(productionBehavior3, testProduction3Pattern);

    // Production type 3M
    String productionBehavior3M = "production:3M";
    this.episodeTypeDescMap.put(productionBehavior3M, 
        "This episode looks like a production episode in test-last because:");
    String testProduction3MPattern = "There are multiple product file edits, and method increases" 
                                    + ", and file size increases more than 100.";
    this.episodeTypePatternMap.put(productionBehavior3M, testProduction3MPattern);

  }
  
  /**
   * Adds pattern for long episodes.
   *
   */
  private void addLongPatterns() {
    // Long type 1
    String longBehavior1 = "long:1";
    this.episodeTypeDescMap.put(longBehavior1, "This episode is a long episode because:");
    String testLong1Pattern = "It had too many activities before tests ran.";
    this.episodeTypePatternMap.put(longBehavior1, testLong1Pattern);
    
    // Long type 2
    String longBehavior2 = "long:2";
    this.episodeTypeDescMap.put(longBehavior2, "This episode is a long episode because:");
    String testLong2Pattern = "It took too much time before tests ran.";
    this.episodeTypePatternMap.put(longBehavior2, testLong2Pattern);
  }
  
  /**
   * Adds pattern for unknown episodes.
   *
   */
  private void addUnknownPatterns() {
    // Unknown type 1
    String unknownBehavior1 = "unknown:1";
    this.episodeTypeDescMap.put(unknownBehavior1, "This episode is unknown because:");

    String testUnknown1Pattern = "No appropriate rules applied for this episode.";
    this.episodeTypePatternMap.put(unknownBehavior1, testUnknown1Pattern);

    
    // Unknown type 2
    String unknownBehavior2 = "unknown:2";
    this.episodeTypeDescMap.put(unknownBehavior2, "This episode is unknown because:");

    String testUnknown2Pattern = "It did not end with successful unit test runs.";
    this.episodeTypePatternMap.put(unknownBehavior2, testUnknown2Pattern);
  }
    
  /**
   * Gets episode type description.
   * 
   * @param name Name of the episode category.
   * @param type Type of the episode category.
   * @return Description of episode category and 
   */
  public String getDesc(String name, String type) {
    return this.episodeTypeDescMap.get(name + ":" + type);
  }
  
  /**
   * Interprets TDD episode inference with parameters.   
   * 
   * @param name Episode name.
   * @param type Episode type
   * @param args Arguments used in the interpretation. 
   * @return A natural string that explains TDD episode reasoning process.
   * @throws SdsaAnalyzerException If number of arguments does not match. 
   */
  public String interpret(String name, String type, String[] args) throws SdsaAnalyzerException {
    // Check number of arguments.
    String pattern = this.episodeTypePatternMap.get(name + ":" + type);
    if (pattern == null) {
       throw new SdsaAnalyzerException("Episode " + name + " of type " + type + 
           " can not be interpreted.");  
    }
    
    String[] fragments = pattern.split("%");
    if (fragments == null || fragments.length - 1 != args.length) {
      int length = fragments.length - 1;
      StringBuffer buf = new StringBuffer("Require ");
      buf.append(length).append(" arguments but the given array only has ");
      buf.append(args.length).append(" arguments : [");
      
      for (int i = 0; i < args.length; i++) {
        buf.append(',').append(args[i]);
      }
      buf.append("] for episode (").append(name).append(", ").append(type).append(')');
      
      throw new SdsaAnalyzerException(buf.toString());
    }
    
    String output = new StringBuffer(pattern).toString();
    for (int i = 0; i < args.length; i++) {
      String variable = "%" + (i + 1);
      output = output.replaceAll(variable, args[i]); 
    }
    
    return output;
  }
}
